/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.spring.web.context.support;

import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.url.IPortalRequestUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.Scope;
import org.springframework.web.context.request.SessionScope;
import org.springframework.web.util.WebUtils;

/**
 * Wraps {@link SessionScope} to provide functionality when no session is available by using a
 * singleton instance.
 */
public class PortalSessionScope implements Scope {
    public static final String DESTRUCTION_CALLBACK_NAME_PREFIX =
            PortalSessionScope.class.getName() + ".DESTRUCTION_CALLBACK.";

    protected final Log logger = LogFactory.getLog(this.getClass());

    private IPortalRequestUtils portalRequestUtils;

    @Autowired
    public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
        this.portalRequestUtils = portalRequestUtils;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String)
     */
    @Override
    public Object resolveContextualObject(String arg0) {
        // TODO implement me!
        return null;
    }
    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, org.springframework.beans.factory.ObjectFactory)
     */
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        final HttpSession session = this.getPortalSesion(true);

        final Object sessionMutex = WebUtils.getSessionMutex(session);
        synchronized (sessionMutex) {
            Object scopedObject = session.getAttribute(name);
            if (scopedObject == null) {
                scopedObject = objectFactory.getObject();
                session.setAttribute(name, scopedObject);
            }

            return scopedObject;
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#getConversationId()
     */
    @Override
    public String getConversationId() {
        final HttpSession session = this.getPortalSesion(false);

        if (session == null) {
            return null;
        }

        return session.getId();
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, java.lang.Runnable)
     */
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        final HttpSession session = this.getPortalSesion(true);
        final DestructionCallbackBindingListener callbackListener =
                new DestructionCallbackBindingListener(callback);
        session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name, callbackListener);
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
     */
    @Override
    public Object remove(String name) {
        final HttpSession session = this.getPortalSesion(false);
        if (session == null) {
            return null;
        }

        final Object sessionMutex = WebUtils.getSessionMutex(session);
        synchronized (sessionMutex) {
            final Object attribute = session.getAttribute(name);
            if (attribute != null) {
                session.removeAttribute(name);
            }

            return attribute;
        }
    }

    protected HttpSession getPortalSesion(boolean create) {
        final HttpServletRequest portalRequest = this.portalRequestUtils.getCurrentPortalRequest();
        return portalRequest.getSession(create);
    }

    /**
     * Adapter that implements the Servlet 2.3 HttpSessionBindingListener interface, wrapping a
     * session destruction callback.
     */
    private static class DestructionCallbackBindingListener
            implements HttpSessionBindingListener, Serializable {
        private static final long serialVersionUID = 1L;

        private final transient Runnable destructionCallback;

        public DestructionCallbackBindingListener(Runnable destructionCallback) {
            this.destructionCallback = destructionCallback;
        }

        @Override
        public void valueBound(HttpSessionBindingEvent event) {}

        @Override
        public void valueUnbound(HttpSessionBindingEvent event) {
            if (this.destructionCallback != null) {
                this.destructionCallback.run();
            }
        }
    }
}
